Sandip Sonawane
Anurag Anand
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
plt.style.use('seaborn')
from sklearn.preprocessing import MinMaxScaler
from collections import Counter
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import warnings
warnings.filterwarnings('ignore')
import time
================================================================================
User information is in the file "users.dat" and is in the following format:
UserID::Gender::Age::Occupation::Zip-code
All demographic information is provided voluntarily by the users and is not checked for accuracy. Only users who have provided some demographic information are included in this data set.
Age is chosen from the following ranges:
Occupation is chosen from the following choices:
users = pd.read_csv('./ml-1m/users.dat', sep='::', header=None, engine='python')
users.columns= ['UserID', 'Gender', 'Age', 'Occupation', 'Zip-code']
users.head()
| UserID | Gender | Age | Occupation | Zip-code | |
|---|---|---|---|---|---|
| 0 | 1 | F | 1 | 10 | 48067 |
| 1 | 2 | M | 56 | 16 | 70072 |
| 2 | 3 | M | 25 | 15 | 55117 |
| 3 | 4 | M | 45 | 7 | 02460 |
| 4 | 5 | M | 25 | 20 | 55455 |
users.describe()
| UserID | Age | Occupation | |
|---|---|---|---|
| count | 6040.000000 | 6040.000000 | 6040.000000 |
| mean | 3020.500000 | 30.639238 | 8.146854 |
| std | 1743.742145 | 12.895962 | 6.329511 |
| min | 1.000000 | 1.000000 | 0.000000 |
| 25% | 1510.750000 | 25.000000 | 3.000000 |
| 50% | 3020.500000 | 25.000000 | 7.000000 |
| 75% | 4530.250000 | 35.000000 | 14.000000 |
| max | 6040.000000 | 56.000000 | 20.000000 |
================================================================================
Movie information is in the file "movies.dat" and is in the following format:
MovieID::Title::Genres
Genres are pipe-separated and are selected from the following genres:
Some MovieIDs do not correspond to a movie due to accidental duplicate entries and/or test entries
movies = pd.read_csv('./ml-1m/movies.dat', sep='::', header=None, engine='python', encoding = "ISO-8859-1")
movies.columns= ['MovieID', 'Title', 'Genres']
movies.head()
| MovieID | Title | Genres | |
|---|---|---|---|
| 0 | 1 | Toy Story (1995) | Animation|Children's|Comedy |
| 1 | 2 | Jumanji (1995) | Adventure|Children's|Fantasy |
| 2 | 3 | Grumpier Old Men (1995) | Comedy|Romance |
| 3 | 4 | Waiting to Exhale (1995) | Comedy|Drama |
| 4 | 5 | Father of the Bride Part II (1995) | Comedy |
list(movies[movies['MovieID']==1]['Title'])[0]
'Toy Story (1995)'
================================================================================
All ratings are contained in the file "ratings.dat" and are in the following format:
UserID::MovieID::Rating::Timestamp
ratings = pd.read_csv('./ml-1m/ratings.dat', sep='::', header=None, engine='python')
ratings.columns= ['UserID', 'MovieID', 'Rating', 'Timestamp']
ratings.drop(columns=['Timestamp'], inplace=True)
ratings.head()
| UserID | MovieID | Rating | |
|---|---|---|---|
| 0 | 1 | 1193 | 5 |
| 1 | 1 | 661 | 3 |
| 2 | 1 | 914 | 3 |
| 3 | 1 | 3408 | 4 |
| 4 | 1 | 2355 | 5 |
ratings.shape
(1000209, 3)
ratings.Rating.describe()
count 1.000209e+06 mean 3.581564e+00 std 1.117102e+00 min 1.000000e+00 25% 3.000000e+00 50% 4.000000e+00 75% 4.000000e+00 max 5.000000e+00 Name: Rating, dtype: float64
Some users give high ratings to almost all movies, however some give very low ratings. To nullify the effect of user preferences, we can normalize the user ratings by substracting the mean rating for each user.
To ensure our ratings columns has positive values, we will normalize it to contain rating between 0 and 1
temp_uid = ratings.groupby(by=['UserID']).mean()
temp_uid.head()
| MovieID | Rating | |
|---|---|---|
| UserID | ||
| 1 | 1560.547170 | 4.188679 |
| 2 | 1784.015504 | 3.713178 |
| 3 | 1787.450980 | 3.901961 |
| 4 | 1932.000000 | 4.190476 |
| 5 | 1762.747475 | 3.146465 |
ratings1 = pd.merge(ratings, temp_uid['Rating'], on='UserID')
ratings1['rating_normalized'] = ratings1.Rating_x - ratings1.Rating_y
def NormalizeData(data):
return (data - np.min(data)) / (np.max(data) - np.min(data))
ratings_normalized = NormalizeData(ratings1['rating_normalized'])
ratings1['rating_normalized'] = ratings_normalized
ratings1 = ratings1.rename(columns={'Rating_x': 'original_rating', 'Rating_y': 'mean_rating_by_user'})
ratings1.head()
| UserID | MovieID | original_rating | mean_rating_by_user | rating_normalized | |
|---|---|---|---|---|---|
| 0 | 1 | 1193 | 5 | 4.188679 | 0.619810 |
| 1 | 1 | 661 | 3 | 4.188679 | 0.356186 |
| 2 | 1 | 914 | 3 | 4.188679 | 0.356186 |
| 3 | 1 | 3408 | 4 | 4.188679 | 0.487998 |
| 4 | 1 | 2355 | 5 | 4.188679 | 0.619810 |
ratings1.groupby(by=['MovieID']).mean()
| UserID | original_rating | mean_rating_by_user | rating_normalized | |
|---|---|---|---|---|
| MovieID | ||||
| 1 | 3053.819933 | 4.146846 | 3.667349 | 0.576072 |
| 2 | 3027.977175 | 3.201141 | 3.525284 | 0.470143 |
| 3 | 2632.156904 | 3.016736 | 3.456983 | 0.454839 |
| 4 | 3268.841176 | 2.729412 | 3.428227 | 0.420756 |
| 5 | 3143.152027 | 3.006757 | 3.426533 | 0.457537 |
| ... | ... | ... | ... | ... |
| 3948 | 2063.107889 | 3.635731 | 3.608575 | 0.516448 |
| 3949 | 2289.046053 | 4.115132 | 3.566000 | 0.585251 |
| 3950 | 2123.370370 | 3.666667 | 3.614114 | 0.519796 |
| 3951 | 1687.925000 | 3.900000 | 3.543063 | 0.559917 |
| 3952 | 2002.659794 | 3.780928 | 3.615590 | 0.534662 |
3706 rows × 4 columns
We can observe from above table how rating_normalized takes care of user preference to always give high ratings or low ratings. Specifically, we can observe row number 3 and 5. Avg. rating for movie 3 is actually high, but after normalization, its lower than movie 5.
temp = ratings1.groupby(by = ['UserID']).mean()
temp.head()
| MovieID | original_rating | mean_rating_by_user | rating_normalized | |
|---|---|---|---|---|
| UserID | ||||
| 1 | 1560.547170 | 4.188679 | 4.188679 | 0.512869 |
| 2 | 1784.015504 | 3.713178 | 3.713178 | 0.512869 |
| 3 | 1787.450980 | 3.901961 | 3.901961 | 0.512869 |
| 4 | 1932.000000 | 4.190476 | 4.190476 | 0.512869 |
| 5 | 1762.747475 | 3.146465 | 3.146465 | 0.512869 |
plt.hist(ratings1['rating_normalized'], bins=70)
plt.title('normalized rating')
plt.show()
ratings1['original_rating'].value_counts(normalize=True) * 100
4 34.889808 3 26.114242 5 22.626271 2 10.753453 1 5.616226 Name: original_rating, dtype: float64
plt.hist(ratings1['original_rating'], bins=10)
plt.title('original Rating')
plt.show()
plt.hist(temp['original_rating'], bins=70)
plt.title('original mean Rating by user')
plt.show()
temp = ratings1.groupby(by=['MovieID']).count()
temp.UserID.describe()
count 3706.000000 mean 269.889099 std 384.047838 min 1.000000 25% 33.000000 50% 123.500000 75% 350.000000 max 3428.000000 Name: UserID, dtype: float64
plt.figure(figsize=[14,6])
tips = sns.load_dataset("tips")
plt.title('ratings per movie distribution')
ax = sns.boxplot(x=temp["UserID"])
temp = ratings1.groupby(by=['UserID']).count()
temp.MovieID.describe()
count 6040.000000 mean 165.597517 std 192.747029 min 20.000000 25% 44.000000 50% 96.000000 75% 208.000000 max 2314.000000 Name: MovieID, dtype: float64
plt.figure(figsize=[14,6])
# sns.set_theme(style="whitegrid")
tips = sns.load_dataset("tips")
plt.title('ratings per user distribution')
ax = sns.boxplot(x=temp["MovieID"])
# plt.axvline(x=33, c='r')
movies.shape
(3883, 3)
len(ratings.MovieID.unique())
3706
There are 177 movies that have not been rated by any user.
all_genres = ['Action','Adventure','Animation','Children','Comedy','Crime','Documentary','Drama','Fantasy','Film-Noir','Horror','Musical','Mystery','Romance','Sci-Fi','Thriller','War','Western']
# ratings1.merge(movies, on='MovieID')
genres_dict = {}
for genre in all_genres:
count = sum(movies.Genres.str.contains(genre))
genres_dict[genre]=count
genres_dict = dict(sorted(genres_dict.items(), key=lambda item: item[1]))
plt.bar(genres_dict.keys(), genres_dict.values())
plt.xticks(rotation=45)
plt.title('No. of movies by Genre')
plt.show()
Most of the movies are Drama and Comedy
master_df = pd.merge(ratings1, movies, on='MovieID')
genres_popularity_dict = {}
for genre in all_genres:
df = master_df[master_df.Genres.str.contains(genre)]
mean = df.original_rating.mean()
genres_popularity_dict[genre]=mean
genres_popularity_dict = dict(sorted(genres_popularity_dict.items(), key=lambda item: item[1]))
plt.bar(genres_popularity_dict.keys(), genres_popularity_dict.values())
plt.xticks(rotation=45)
plt.title('Average Rating by Genre')
plt.show()
Film-Noir has highest average rating, but number of such movies are very less compared to other genre. It is followed by Documentary.
Users will select their choice of Genre and the highly rated movies will be recommended to the user based on selection of Genre.
To consider a movie's rating, it has to have reaceived at least 300 ratings. This will ensure that the quality of results will be good.
For documentary genre, there are less number of ratings to movies. Hence, specifically for documentary genre, the threshold for minimum number of movie ratings is lowered to 80.
In this scheme, we can consider the year at which the movie was released. Older movies will get slightly less points for recommendation compared to recent movies.
With this, recent movies will be showed to the user based on genre. We need to optimize how much we want to penalize a movie so that there will be some very good movies that are old will be recommended.
# choose Genre
gen = 'Comedy'
# count the number of ratings each movies has received
mcount = master_df.groupby(by=['MovieID']).count()[['Genres']]
mcount = mcount.rename(columns={'Genres':'number_of_ratings'})
master_df1 = master_df.merge(mcount, on='MovieID')
# filter by Genre and select movies that have recieve at-least 300 ratings
filt = (master_df1.Genres.str.contains(gen)) & (master_df1['number_of_ratings']>300)
master_df2 = master_df1[filt]
top_movies_by_Genere = master_df2.groupby(by=['MovieID']).mean().sort_values(by=['rating_normalized'], ascending=False)
top_movies_by_Genere.index[:30]
Int64Index([ 745, 1148, 1223, 1136, 2858, 2324, 1197, 1234, 1278, 910, 2804,
905, 3897, 1172, 3114, 898, 1276, 951, 916, 1449, 1269, 1288,
911, 3462, 1304, 1256, 1, 2788, 2918, 2997],
dtype='int64', name='MovieID')
Will you normalize the rating matrix? If so, which normalization option do you use?
What's the nearest neighborhood size you use?
Which similarity metric do you use?
If you say prediction is based on a "weighted average", then explain what weights you use.
Will you still have missing values after running the algorithm? If so, how do you handle those missing values?
master_df.columns
Index(['UserID', 'MovieID', 'original_rating', 'mean_rating_by_user',
'rating_normalized', 'Title', 'Genres'],
dtype='object')
mat = master_df.pivot_table(index='UserID',columns='MovieID',values='rating_normalized').T
mat.head()
| UserID | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ... | 6031 | 6032 | 6033 | 6034 | 6035 | 6036 | 6037 | 6038 | 6039 | 6040 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| MovieID | |||||||||||||||||||||
| 1 | 0.61981 | NaN | NaN | NaN | NaN | 0.525864 | NaN | 0.528041 | 0.679499 | 0.62956 | ... | NaN | 0.495125 | NaN | NaN | 0.695993 | NaN | NaN | NaN | NaN | 0.436719 |
| 2 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.62956 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | 0.300557 | NaN | NaN | NaN | NaN | NaN |
| 4 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.396229 | NaN | NaN | ... | NaN | NaN | NaN | NaN | 0.432369 | 0.341127 | NaN | NaN | NaN | NaN |
| 5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | 0.300557 | NaN | NaN | NaN | NaN | NaN |
5 rows × 6040 columns
mat.shape
(3706, 6040)
mat.iloc[:, 0]
MovieID
1 0.61981
2 NaN
3 NaN
4 NaN
5 NaN
...
3948 NaN
3949 NaN
3950 NaN
3951 NaN
3952 NaN
Name: 1, Length: 3706, dtype: float64
master_df[(master_df.UserID==1) & (master_df.MovieID==1)]
| UserID | MovieID | original_rating | mean_rating_by_user | rating_normalized | Title | Genres | |
|---|---|---|---|---|---|---|---|
| 41626 | 1 | 1 | 5 | 4.188679 | 0.61981 | Toy Story (1995) | Animation|Children's|Comedy |
mat.corrwith(mat.iloc[:, 0])
UserID
1 1.000000
2 0.416667
3 -0.332182
4 0.333333
5 -0.172516
...
6036 -0.201132
6037 -0.451754
6038 NaN
6039 0.056857
6040 -0.043519
Length: 6040, dtype: float64
# %%timeit
correlation_with_user = mat.corrwith(mat.iloc[:, 2])
corred = pd.DataFrame(correlation_with_user,columns=['Corr'])
corred.dropna(inplace=True)
corred.head()
| Corr | |
|---|---|
| UserID | |
| 1 | -0.332182 |
| 2 | 0.236834 |
| 3 | 1.000000 |
| 4 | 0.840168 |
| 5 | -0.437621 |
sum(corred.sort_values('Corr',ascending=False)['Corr']==1)
102
corred.sort_values('Corr',ascending=False).head()
| Corr | |
|---|---|
| UserID | |
| 995 | 1.0 |
| 3407 | 1.0 |
| 5587 | 1.0 |
| 3275 | 1.0 |
| 3215 | 1.0 |
corred = corred.sort_values('Corr',ascending=False)
pred_df = pd.DataFrame()
for i in range(100):
pred_df[corred.index[i]] = mat.loc[:, corred.index[i]]
pred_df['mean'] = pred_df.mean(axis=1)
pred_df.sort_values('mean', ascending=False)
| 995 | 3407 | 5587 | 3275 | 3215 | 3109 | 992 | 3040 | 4783 | 2871 | ... | 5929 | 2193 | 1240 | 2183 | 5030 | 1316 | 1006 | 1575 | 5052 | mean | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| MovieID | |||||||||||||||||||||
| 1175 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.798997 |
| 3491 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.798997 |
| 1780 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.798997 |
| 2261 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.798997 |
| 2065 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.798997 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3943 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3944 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3945 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3950 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3951 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3706 rows × 101 columns
pred_df['mean']
MovieID
1 0.545037
2 0.455694
3 0.579783
4 0.438724
5 0.371293
...
3948 0.487251
3949 0.571586
3950 NaN
3951 NaN
3952 0.227276
Name: mean, Length: 3706, dtype: float64
mat.iloc[:, 2]
MovieID
1 NaN
2 NaN
3 NaN
4 NaN
5 NaN
..
3948 NaN
3949 NaN
3950 NaN
3951 NaN
3952 NaN
Name: 3, Length: 3706, dtype: float64
# np.sqrt(np.nanmean((np.array(y_true) - np.array(y_pred))**2, ))
np.sqrt(np.nanmean((np.array(mat.iloc[:, 2]) - np.array(pred_df['mean']))**2))
0.10219359786296539
len(master_df.UserID.unique())
6040
trn_users = np.random.choice(master_df.UserID.unique(),
int(len(master_df.UserID.unique())*0.95), replace=False)
len(trn_users)
5738
trn = master_df.loc[master_df.UserID.isin(trn_users), :]
tst = master_df.loc[~master_df.UserID.isin(trn_users), :]
len(trn), len(tst)
(946469, 53740)
trn_mat = trn.pivot_table(index='UserID',columns='MovieID',values='rating_normalized').T
tst_mat = tst.pivot_table(index='UserID',columns='MovieID',values='rating_normalized').T
len(tst_mat.columns)
302
tst_mat.iloc[:, :]
| UserID | 33 | 44 | 61 | 70 | 72 | 98 | 137 | 154 | 158 | 164 | ... | 5848 | 5876 | 5885 | 5888 | 5906 | 5910 | 5914 | 5922 | 5931 | 6032 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| MovieID | |||||||||||||||||||||
| 1 | NaN | 0.693171 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | 0.419085 | NaN | NaN | 0.752352 | NaN | NaN | NaN | NaN | 0.571071 | 0.495125 |
| 2 | NaN | 0.561359 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | 0.550898 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.571071 | NaN |
| 3 | NaN | NaN | NaN | NaN | NaN | NaN | 0.695832 | NaN | NaN | NaN | ... | NaN | NaN | NaN | 0.225104 | NaN | NaN | NaN | NaN | NaN | NaN |
| 4 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 5 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | 0.356916 | NaN | NaN | NaN | NaN | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3948 | NaN | NaN | 0.673972 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3949 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3950 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3951 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3952 | NaN | NaN | NaN | NaN | 0.684531 | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3093 rows × 302 columns
test_rmse_dict = {}
test_rmse_dict[0] = []
for t in range(0, len(tst_mat.columns)):
# calculate correlation with each user in training set for user in test set
correlation_with_user = trn_mat.corrwith(tst_mat.iloc[:, t])
corred = pd.DataFrame(correlation_with_user,columns=['Corr'])
corred.dropna(inplace=True)
corred = corred.sort_values('Corr',ascending=False)
pred_df = pd.DataFrame()
# take nearest users and average their rating row-wise to come up with final prediction
for i in range(10):
pred_df[corred.index[i]] = trn_mat.loc[:, corred.index[i]]
pred_df['mean_rating_pred'] = pred_df.mean(axis=1)
# select only those movies that are there in our test set
pred_df = pred_df.iloc[pred_df.index.isin(tst_mat.index),]
# drop movies in our test set which were not available in train set
idx = tst_mat.index[~tst_mat.index.isin(pred_df.index)]
tst_mat.drop(idx, axis=0, inplace=True)
rmse = np.sqrt(np.nanmean((np.array(tst_mat.iloc[:, t]) - np.array(pred_df['mean_rating_pred']))**2))
test_rmse_dict[0].append(rmse)
t_start = time.time()
plt.figure(figsize=[14,6])
tips = sns.load_dataset("tips")
ax = sns.boxplot(x=test_rmse_dict[0])
t_end = time.time()
t_end - t_start
0.025951147079467773
test_rmse_dict = {}
t_start = time.time()
for test_set in range(10):
# randomly select train-test users
trn_users = np.random.choice(master_df.UserID.unique(),
int(len(master_df.UserID.unique())*0.95), replace=False)
trn = master_df.loc[master_df.UserID.isin(trn_users), :]
tst = master_df.loc[~master_df.UserID.isin(trn_users), :]
trn_mat = trn.pivot_table(index='UserID',columns='MovieID',values='rating_normalized').T
tst_mat = tst.pivot_table(index='UserID',columns='MovieID',values='rating_normalized').T
test_rmse_dict[test_set] = []
for t in range(0, len(tst_mat.columns)):
# calculate correlation with each user in training set for user in test set
correlation_with_user = trn_mat.corrwith(tst_mat.iloc[:, t])
corred = pd.DataFrame(correlation_with_user,columns=['Corr'])
corred.dropna(inplace=True)
corred = corred.sort_values('Corr',ascending=False)
pred_df = pd.DataFrame()
# take nearest users and average their rating row-wise to come up with final prediction
for i in range(10):
pred_df[corred.index[i]] = trn_mat.loc[:, corred.index[i]]
pred_df['mean_rating_pred'] = pred_df.mean(axis=1)
# select only those movies that are there in our test set
pred_df = pred_df.iloc[pred_df.index.isin(tst_mat.index),]
# drop movies in our test set which were not available in train set
idx = tst_mat.index[~tst_mat.index.isin(pred_df.index)]
tst_mat.drop(idx, axis=0, inplace=True)
rmse = np.sqrt(np.nanmean((np.array(tst_mat.iloc[:, t]) - np.array(pred_df['mean_rating_pred']))**2))
test_rmse_dict[test_set].append(rmse)
print(test_set)
t_end = time.time()
# test_result = pd.DataFrame.from_dict(test_rmse_dict)
# test_result.to_csv('tst_result.csv')
test_result = pd.read_csv('tst_result.csv')
test_result.drop(columns='Unnamed: 0', inplace=True)
plt.title('RMSE distribution for each test-train set: UBCF')
ax = sns.boxplot(data=test_result)
# create training matrix
trn_mat = master_df.pivot_table(index='UserID',columns='MovieID',values='rating_normalized').T
new_user = mat.iloc[:, 0]
# find correlation with users ratings
correlation_with_user = trn_mat.corrwith(NormalizeData(new_user - new_user.mean()))
corred = pd.DataFrame(correlation_with_user,columns=['Corr'])
corred.dropna(inplace=True)
corred = corred.sort_values('Corr',ascending=False)
pred_df = pd.DataFrame()
# take nearest users and average their rating row-wise to come up with final prediction
for i in range(10):
pred_df[corred.index[i]] = trn_mat.loc[:, corred.index[i]]
pred_df['mean_rating_pred'] = pred_df.mean(axis=1)
corred.index
Int64Index([5863, 2441, 98, 4649, 5993, 5215, 3396, 2207, 5681, 5174,
...
2514, 5819, 4191, 3234, 5877, 1886, 619, 804, 5800, 1549],
dtype='int64', name='UserID', length=6031)
# sort by popularity to predict movies to the new user
pred_df = pred_df.sort_values(by=['mean_rating_pred'], ascending=False)
pred_df
| 5863 | 2441 | 98 | 4649 | 5993 | 5215 | 3396 | 2207 | 5681 | 5174 | mean_rating_pred | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| MovieID | |||||||||||
| 1947 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| 930 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| 908 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| 904 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| 1035 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3947 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3949 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3950 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3951 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3952 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3706 rows × 11 columns
# new_user.iloc[:, 0] = np.nan
# new_user.values = [np.nan for x in new_user.values]
new_user[new_user.values != 0] = np.nan
new_user[new_user.index==2] = 5
new_user[new_user.index==3] = 3
new_user[new_user.index==5] = 5
new_user[new_user.index==15] = 4
new_user[new_user.index==9] = 3
new_user[new_user.index==125] = 2
new_user.mean()
3.6666666666666665
NormalizeData(new_user - new_user.mean())
MovieID
1 NaN
2 1.000000
3 0.333333
4 NaN
5 1.000000
...
3948 NaN
3949 NaN
3950 NaN
3951 NaN
3952 NaN
Name: 1, Length: 3706, dtype: float64
pred_df.index[:30]
Int64Index([1947, 930, 908, 904, 1035, 1175, 1234, 1278, 678, 608, 3114,
318, 3578, 1218, 1036, 3785, 3265, 180, 3175, 2906, 2881, 3129,
3055, 3007, 3163, 720, 1207, 2791, 2762, 113],
dtype='int64', name='MovieID')
pred_df.dropna(axis=0, how='all')
| 5863 | 2441 | 98 | 4649 | 5993 | 5215 | 3396 | 2207 | 5681 | 5174 | mean_rating_pred | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| MovieID | |||||||||||
| 1947 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| 930 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| 908 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| 904 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| 1035 | NaN | NaN | NaN | NaN | 0.701799 | NaN | NaN | NaN | NaN | NaN | 0.701799 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 3036 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.165364 | NaN | 0.165364 |
| 2657 | NaN | NaN | 0.143795 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.143795 |
| 3146 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.117433 | 0.117433 |
| 1544 | NaN | 0.107669 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.107669 |
| 1711 | NaN | 0.107669 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 0.107669 |
214 rows × 11 columns
Above movie IDs will be recommended to the new user.
Will you normalize the rating matrix? If so, which normalization option do you use?
What's the nearest neighborhood size you use?
Which similarity metric do you use?
If you say prediction is based on a "weighted average", then explain what weights you use.
Will you still have missing values after running the algorithm? If so, how do you handle those missing values?
mat = master_df.pivot_table(index='UserID',columns='MovieID',values='rating_normalized')
mat.head()
| MovieID | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ... | 3943 | 3944 | 3945 | 3946 | 3947 | 3948 | 3949 | 3950 | 3951 | 3952 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| UserID | |||||||||||||||||||||
| 1 | 0.61981 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 2 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 4 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 5 | NaN | NaN | NaN | NaN | NaN | 0.361751 | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
5 rows × 3706 columns
mat.corrwith(mat.iloc[:, 0])
MovieID
1 1.000000
2 0.100095
3 0.083085
4 0.260036
5 0.006288
...
3948 0.054597
3949 0.170666
3950 0.071282
3951 0.094302
3952 0.065601
Length: 3706, dtype: float64
master_df.head(1)
| UserID | MovieID | original_rating | mean_rating_by_user | rating_normalized | Title | Genres | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 1193 | 5 | 4.188679 | 0.61981 | One Flew Over the Cuckoo's Nest (1975) | Drama |
test_rmse_dict = {}
t_start = time.time()
for test_set in range(10):
# randomly select train-test users
trn_movies = np.random.choice(master_df.MovieID.unique(),
int(len(master_df.MovieID.unique())*0.95), replace=False)
trn = master_df.loc[master_df.MovieID.isin(trn_movies), :]
tst = master_df.loc[~master_df.MovieID.isin(trn_movies), :]
trn_mat = trn.pivot_table(index='UserID',columns='MovieID',values='rating_normalized')
tst_mat = tst.pivot_table(index='UserID',columns='MovieID',values='rating_normalized')
test_rmse_dict[test_set] = []
for t in range(0, len(tst_mat.columns)):
# calculate correlation with each user in training set for user in test set
correlation_with_movie = trn_mat.corrwith(tst_mat.iloc[:, t])
corred = pd.DataFrame(correlation_with_movie,columns=['Corr'])
corred.dropna(inplace=True)
corred = corred.sort_values('Corr',ascending=False)
pred_df = pd.DataFrame()
# take nearest movies and average their rating row-wise to come up with final prediction
try:
for i in range(3):
pred_df[corred.index[i]] = trn_mat.loc[:, corred.index[i]]
except:
continue
pred_df['mean_rating_pred'] = pred_df.mean(axis=1)
# select only those movies that are there in our test set
pred_df = pred_df.iloc[pred_df.index.isin(tst_mat.index),]
# drop movies in our test set which were not available in train set
idx = tst_mat.index[~tst_mat.index.isin(pred_df.index)]
tst_mat.drop(idx, axis=0, inplace=True)
rmse = np.sqrt(np.nanmean((np.array(tst_mat.iloc[:, t]) - np.array(pred_df['mean_rating_pred']))**2))
test_rmse_dict[test_set].append(rmse)
print(test_set)
t_end = time.time()
0 1 2 3 4 5 6 7 8 9
# test_result = pd.DataFrame.from_dict(test_rmse_dict1)
# test_result.to_csv('tst_result_IBCF.csv')
test_result = pd.read_csv('tst_result_IBCF.csv')
test_result.drop(columns='Unnamed: 0', inplace=True)
plt.title('RMSE distribution for each test-train set: IBCF')
ax = sns.boxplot(data=test_result)
As we can see from the below plots, UBCF has lower RMSE. Hence this model was chosen for deployment.
plt.figure(figsize=[10,5])
plt.subplot(121)
test_result = pd.read_csv('tst_result.csv')
test_result.drop(columns='Unnamed: 0', inplace=True)
plt.title('RMSE distribution for each test-train set: UBCF')
ax = sns.boxplot(data=test_result)
plt.ylim([-0.01,0.5])
plt.subplot(122)
test_result = pd.read_csv('tst_result_IBCF.csv')
test_result.drop(columns='Unnamed: 0', inplace=True)
plt.title('RMSE distribution for each test-train set: IBCF')
ax = sns.boxplot(data=test_result)
plt.ylim([-0.01,0.5])
plt.tight_layout()
Backend: Python, Django framework, Django plotly dash module
Frontend: JavaScript, CSS, HTML
Server: AWS EC2 Instance small
System I: http://18.191.179.113:8000/movie_recommender/genre
OR
http://ec2-18-191-179-113.us-east-2.compute.amazonaws.com:8000/movie_recommender/genre
System II: http://18.191.179.113:8000/movie_recommender/
OR
http://ec2-18-191-179-113.us-east-2.compute.amazonaws.com:8000/movie_recommender/
Note: Please allow some time for the page to load.